
/*
Borrow rule: 
  1. create object ;
  2. borrow mutable & immutable 
    r1> if immutably borrowed =>  only immutably borrowed allowed with n-times
    r2> if mutably borrowed => cannot borrowed 
    r3> do r1|r2 borrow if last borrow finished
*/


fn eat_box_i32(boxed_i32: Box<i32>){
    println!("Destroying box that constains {}", boxed_i32);
}
fn borrow_i32(borrowed_i32: &i32){
    println!("This int is: {}", borrowed_i32);
}

fn base() {
    let boxed_i32 = Box::new(5_i32); 
    let stacked_i32 = 6_i32;

    borrow_i32(&boxed_i32);
    borrow_i32(&stacked_i32);

    {
        let _ref_to_i32: &i32 = &boxed_i32; //immutable borrow
        //eat_box_i32(boxed_i32);  // cann't destroy the boxed_i32; means cann't mutable borrow the boxed_i32

        borrow_i32(_ref_to_i32); // immutable borrow
    }

    eat_box_i32(boxed_i32);
}

//#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
struct Book {
    title: &'static str,
    year: u32,
}
fn borrow_book(book: &Book){
    println!("I immutably borrowed {} - {} edition", book.title, book.year);
}
fn new_edition(book: &mut Book){
    book.year = 2014;
    println!("I mutably borrowed {} - {} edition", book.title, book.year);
}

fn mutability(){
    //declare here
    let immutabook = Book { 
        title: "Godel, Escher, Bach",
        year: 1979,
    };
    borrow_book(&immutabook); //immutably borrow from immutable object;

    let mut mutabook = immutabook; //create an mutabook copy from immutable object

    borrow_book(&mutabook); // immutably borrow from mutable object


    new_edition(&mut mutabook);//mutably borrow from mutable object

    //new_edition(&mut immutabook); // mutably borrow from immutably object failed!!!!!
}

fn borrow_rules(){
    // declare for mut origin object create
    let mut mut_origin = Book {
        title: "Godel, Escher, Bach",
        year : 1979,
    };

    //rule 1> if immutably borrowed in use
    //  =>  only immutably borrowed allowed with n-times
    //  => mutably borrowed failed 
    {
        let immut_borrowed = &mut_origin;


        //Note: !!!!!!  commented
        //Failed, if immut_borrowed is in use
        //let mut mut_borrowed = &mut mut_origin; 
        //mut_borrowed.title = "Change it";

        // immutably borrow in use
        println!("immutably borrow ok: {:?}", immut_borrowed); 
    }
    //rule 2> if mutably borrowed in use => cannot immutably or other mutably borrowed 
    {
        let mut_borrowed: &mut Book = &mut mut_origin;
            
        //faile if uncommented
        //println!("immutably borrow failed: {:?}", mut_origin);

        //let mut new_mut_borrowed = &mut mut_origin;
        // new_mut_borrowed.title = "new change";


        mut_borrowed.title = "Change again!";
    }
    //rule 3> do r1|r2 borrow if last borrow finished
    {
        //3.1  immutably borrow after mutably borrowed [finished]
        let mut_borrowed: &mut Book = &mut mut_origin;
        mut_borrowed.title = "Hello world";
        
        //can immutably borrow when mutably borrowed finished ( mut_borrowed isn't used next)
        println!("immutably borrow ok: {:?}", mut_origin); //OK

        //mutably borrowed is in used -- commented
        //mut_borrowed.title = "book in used";
    }
    {
        //3.2 can mutably borrow after immutably borrowed [finished]
        let immut_borrowed = &mut_origin;
        println!("immutably borrow ok: {:?}", immut_borrowed);
        
        let immut_borrowed_2: &mut Book = &mut mut_origin;
        immut_borrowed_2.title = "change 3 times";
        

        //Note: if this uncommented, last mut borrow failed
        //println!("immutably borrow ok: {:?}", immut_borrowed);
    }
}



/*  aliasing 
 *  1. Data can be immutably borrowed any number of times
 *  (but while immutably borrowed, the original data can't be mutably borrowed.)
 *  2.Only one mutable borrow is allowed at a time.
 *  3. original data can be borrowed again only after the mutable reference has been used for the
 *     last time
 */

//#[derive(Clone, Copy)]
struct Point { 
    x: i32, 
    y: i32, 
    z: i32,
}

fn aliasing(){
    let mut point = Point { x: 0, y: 0, z: 0};

    let borrowed_point = &point;
    let another_borrow = &point;

    // immutably borrowed any number of times
    println!("Point has coordinates: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z);

    // Error:  cann't borrow point as mutable, because it is borrowed as immutable
    // let mutable_borrow = &mut point;

    println!("Point has coordinates: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z);


    let mutable_borrow = &mut point;
    mutable_borrow.x = 5; 
    mutable_borrow.y = 2; 
    mutable_borrow.z = 1; 
    
    //Error: Can't borrow point as immutable, because it is borrowed as mutable
    // let y = &point.y;

    println!("Point has coordinates: ({}, {}, {})", mutable_borrow.x, mutable_borrow.y, mutable_borrow.z);


    let new_borrowed_point = &point;
    println!("Point has coordinates: ({}, {}, {})", new_borrowed_point.x, new_borrowed_point.y, new_borrowed_point.z);
}

//pattern matching | destructuring  via the "let" binding, 
//the ref keyword can be used to take ref to the fields of struct/tuple.


fn ref_borrow(){
    //TODO:
    let c = 'Q';

    let ref ref_c1 = c; // equal to  let ref_c1 = &c;
    let ref_c2 = &c;
    println!("ref_c1 equal ref_c2: {}", *ref_c1 == *ref_c2);

    let point = Point {x: 0, y: 0, z: 0};

    //destructuring a struct -> ref is ok
    let _copy_of_x = {
        let Point {x: ref ref_to_x, y: _, z: _} = point;
        *ref_to_x
    };

    // moved ownership
    let mut mutable_point = point;
    {
        let Point {x:_, y: ref mut mut_ref_to_y, z:_ } = mutable_point;
        *mut_ref_to_y = 1;
    }

    println!("point is ({}, {}, {})", mutable_point.x, mutable_point.y, mutable_point.z);


    let mut mutable_tuple = (Box::new(5u32), 3u32);
    {
        let (_, ref mut last) = mutable_tuple;
        *last = 2u32;
    }
    println!("tuple is {:?}", mutable_tuple);
}

// case 1: 
//  object-init;  { 
//      mut_borrow 
//      immut_borrow //compile error
//      free mut_borrow
//  }
//  case 2: 
//
//  object -init; {
//      immut_borrow
//      mut_borrow  //compile error
//      free immu_borrow
//  }
//  case 1 + case 2 => violate the  mut_borrow only once
fn mut_borrow(){
    let mut var = String::from("strings"); //object
    {

        let mut_ref_var =  &mut var; //mut_ref first
        //compile error 
        //let immut_ref_var = &var; //immut_ref at same time
        //println!("immut_ref_var: {}", immut_ref_var);

        println!("mut_ref_var: {}", mut_ref_var);
    }
    {
        let imm_ref_var = &var;

        //compile error
        // let mut_ref_var = &mut var;
        // println!("mut_ref_var: {}", mut_ref_var);

        println!("imm_ref_var: {}", imm_ref_var);
    }

    {
        /*
        let out_var = String:from("out_strings");

        //only capture the out_var for use; 
        //var will be pass in the calling senario
        Var::.func(&var,  move |var| {
            var.call();
            out_var.len();//moved
        });
        */
    }
}

fn deref_ownership() {
    { //read only
        let s = Some(String::from("deref_ownership"));
        let ref_s = &s;
        //ref_s read;
        match *ref_s {
            None => { println!("None");},
            Some(ref x) => {
                println!("{}", x);
            }
        }
        println!("s is exist: {}", s.unwrap());
    }

    { //mutable 
        let mut _s = String::from("hello");
        
        //let mut ref_s = &mut s; //  1. object is borrowed with readonly
        //println!("ref_s is ok: {}", ref_s);
        
        //NOTE: 2. want move an borrowed object to new location?  compile error!!!!!
        //let new = *ref_s; //3. move action definitely 
        
        //println!("move ref_s object to new object : {}", new);

        //println!("original s is moved???: {}", s);
    }
    
}


pub fn main(){
    base();

    mutability();

    borrow_rules();

    aliasing();

    ref_borrow();


    mut_borrow();

    deref_ownership();
}
